﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

using Stateless;
using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Common.Locator;
using Fuse8.DomainFramework.Domain;

namespace Fuse8.ContentManagementFramework.SystemFacade.Workflow
{
    public abstract class CmsWorkflowStateMachine<TEntity, TKey> : ICmsWorkflowStateMachine<TEntity, TKey>
        where TEntity : class, IEntityObject<TKey>
        where TKey : IComparable<TKey>, IEquatable<TKey>
    {
        #region Fields

        private static object _syncRoot = new object();
        private bool _isStarted;

        private ICmsWorkflowStorageService<TEntity, TKey> _storageService;

        private StateMachine<CmsWorkflowState, CmsWorkflowStateTransition> _stateMachine;
        private CmsWorkflowState _currentState;

        #endregion

        #region Properties

        protected TEntity Entity { get; set; }

        #endregion

        #region ICmsWorkflowStateMachine Members

        public CmsWorkflowState CurrentWorkflowState
        {
            get
            {
                return _stateMachine.State;
            }
        }

        public abstract CmsWorkflowState InitialWorkflowState { get; }

        public abstract IEnumerable<CmsWorkflowState> AllWorkflowStates { get; }

        public ReadOnlyCollection<CmsWorkflowStateTransition> AvailableTransitions
        {
            get
            {
                return new ReadOnlyCollection<CmsWorkflowStateTransition>(
                    _stateMachine.PermittedTriggers
                        .ToList());
            }
        }

        public virtual void Create(DateTime? approvalDeadline, DateTime? reviewDeadline)
        {
            if (_isStarted)
            {
                _storageService.Create(Entity, approvalDeadline, reviewDeadline);
            }
            else
            { 
                //TO DO: start workflow at first
            }
        }

        public virtual void Start(TEntity entity)
        {
            Guard.ArgumentNotNull(entity, "entity");

            if (!_isStarted)
            {
                Entity = entity;

                _storageService = DependencyServiceLocator.Current.GetInstance<ICmsWorkflowStorageService<TEntity, TKey>>();

                _currentState = _storageService.Load(entity, AllWorkflowStates) ?? InitialWorkflowState;

                _stateMachine = new StateMachine<CmsWorkflowState, CmsWorkflowStateTransition>(_currentState);

                Init(_stateMachine);

                _isStarted = true;
            }
        }

        public virtual void Stop()
        {
            if (_isStarted)
            {
                _storageService.Save(Entity, CurrentWorkflowState);

                _isStarted = false;
            }
        }

        public virtual void MakeTransition(CmsWorkflowStateTransition transition, string notes, int? assignedUserId = null)
        {
            Guard.ArgumentNotNull(transition, "transition");

            var fromState = CurrentWorkflowState;

            if (fromState.Equals(InitialWorkflowState))
            {
                fromState.Run();
            }
            
            _stateMachine.Fire(transition);

            var toState = CurrentWorkflowState;

            transition.Run(fromState, toState);

            toState.UserId = assignedUserId;
            toState.Notes = notes;
            toState.Run();
        }

        public virtual void Withdraw()
        {
            if (_isStarted)
            {
                _storageService = DependencyServiceLocator.Current.GetInstance<ICmsWorkflowStorageService<TEntity, TKey>>();

                _currentState = InitialWorkflowState;

                _stateMachine = new StateMachine<CmsWorkflowState, CmsWorkflowStateTransition>(_currentState);

                Init(_stateMachine);
            }            
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                Stop();

                _stateMachine = null;
            }
        }

        #endregion

        #region Another methods

        protected virtual void Init(StateMachine<CmsWorkflowState, CmsWorkflowStateTransition> stateMachine)
        { }

        #endregion
    }
}
